winsafe\gui\native_controls/
tab.rs

1use std::any::Any;
2use std::marker::PhantomPinned;
3use std::pin::Pin;
4use std::sync::Arc;
5
6use crate::co;
7use crate::decl::*;
8use crate::guard::*;
9use crate::gui::{collections::*, events::*, privs::*, *};
10use crate::msg::*;
11use crate::prelude::*;
12
13struct TabObj {
14	base: BaseCtrl,
15	events: TabEvents,
16	children: Vec<(String, Box<dyn AsRef<WindowControl>>)>, // title + content
17	_pin: PhantomPinned,
18}
19
20native_ctrl! { Tab: TabObj => TabEvents;
21	/// Native
22	/// [tab](https://learn.microsoft.com/en-us/windows/win32/controls/tab-controls)
23	/// control.
24}
25
26impl Tab {
27	/// Instantiates a new `Tab` object, to be created on the parent window with
28	/// [`HWND::CreateWindowEx`](crate::HWND::CreateWindowEx).
29	///
30	/// # Panics
31	///
32	/// Panics if the parent window was already created – that is, you cannot
33	/// dynamically create a `Tab` in an event closure.
34	#[must_use]
35	pub fn new(parent: &(impl GuiParent + 'static), opts: TabOpts) -> Self {
36		let mut opts = opts;
37		let ctrl_id = auto_id::set_if_zero(opts.ctrl_id);
38		let children = opts.items.drain(..).collect::<Vec<_>>();
39
40		let new_self = Self(Arc::pin(TabObj {
41			base: BaseCtrl::new(ctrl_id),
42			events: TabEvents::new(parent, ctrl_id),
43			children,
44			_pin: PhantomPinned,
45		}));
46
47		let self2 = new_self.clone();
48		let parent2 = parent.clone();
49		parent
50			.as_ref()
51			.before_on()
52			.wm(parent.as_ref().wnd_ty().creation_msg(), move |_| {
53				self2.0.base.create_window(
54					opts.window_ex_style,
55					"SysTabControl32",
56					None,
57					opts.window_style | opts.control_style.into(),
58					opts.position.into(),
59					opts.size.into(),
60					&parent2,
61				);
62				ui_font::set(self2.hwnd());
63				if opts.control_ex_style != co::TCS_EX::NoValue {
64					self2.set_extended_style(true, opts.control_ex_style);
65				}
66				self2.0.children.iter().for_each(|(text, _)| unsafe {
67					self2.items().add(text); // add the tabs
68				});
69				self2.display_tab(0); // 1st tab selected by default
70				parent2
71					.as_ref()
72					.add_to_layout(self2.hwnd(), opts.resize_behavior);
73				Ok(0) // ignored
74			});
75
76		new_self.default_message_handlers(parent);
77		new_self
78	}
79
80	/// Instantiates a new `Tab` object, to be loaded from a dialog resource
81	/// with [`HWND::GetDlgItem`](crate::HWND::GetDlgItem).
82	///
83	/// # Panics
84	///
85	/// Panics if the parent dialog was already created – that is, you cannot
86	/// dynamically create a `Tab` in an event closure.
87	#[must_use]
88	pub fn new_dlg(
89		parent: &(impl GuiParent + 'static),
90		ctrl_id: u16,
91		resize_behavior: (Horz, Vert),
92		items: Vec<(String, Box<dyn AsRef<WindowControl>>)>,
93	) -> Self {
94		let new_self = Self(Arc::pin(TabObj {
95			base: BaseCtrl::new(ctrl_id),
96			events: TabEvents::new(parent, ctrl_id),
97			children: items,
98			_pin: PhantomPinned,
99		}));
100
101		let self2 = new_self.clone();
102		let parent2 = parent.clone();
103		parent.as_ref().before_on().wm_init_dialog(move |_| {
104			self2.0.base.assign_dlg(&parent2);
105			self2.0.children.iter().for_each(|(text, _)| unsafe {
106				self2.items().add(text); // add the tabs
107			});
108			self2.display_tab(0); // 1st tab selected by default
109			parent2
110				.as_ref()
111				.add_to_layout(self2.hwnd(), resize_behavior);
112			Ok(true) // ignored
113		});
114
115		new_self.default_message_handlers(parent);
116		new_self
117	}
118
119	fn default_message_handlers(&self, parent: &impl AsRef<BaseWnd>) {
120		let self2 = self.clone();
121		parent
122			.as_ref()
123			.before_on()
124			.wm_notify(self.ctrl_id(), co::TCN::SELCHANGE, move |_| {
125				if let Some(sel_item) = self2.items().selected() {
126					self2.display_tab(sel_item.index());
127				}
128				Ok(0) // ignored
129			});
130
131		let self2 = self.clone();
132		parent.as_ref().after_on().wm_destroy(move || {
133			unsafe {
134				self2.hwnd().SendMessage(tcm::GetImageList {}).map(|h| {
135					self2
136						.hwnd()
137						.SendMessage(tcm::SetImageList { himagelist: None }); // remove from control
138					let _ = ImageListDestroyGuard::new(h); // destroy
139				});
140			}
141			Ok(())
142		});
143	}
144
145	fn display_tab(&self, index: u32) {
146		self.0
147			.children
148			.iter()
149			.enumerate()
150			.filter(|(i, _)| *i != index as usize)
151			.for_each(|(_, (_, item))| {
152				item.as_ref().as_ref().hwnd().ShowWindow(co::SW::HIDE); // hide all others
153			});
154
155		if let Some((_, item)) = self.0.children.get(index as usize) {
156			let mut rc = self
157				.hwnd()
158				.GetParent()
159				.expect(DONTFAIL)
160				.ScreenToClientRc(self.hwnd().GetWindowRect().expect(DONTFAIL))
161				.expect(DONTFAIL);
162
163			unsafe {
164				self.hwnd().SendMessage(tcm::AdjustRect {
165					display_rect: false,
166					rect: &mut rc, // ideal size of the child
167				});
168			}
169
170			item.as_ref()
171				.as_ref()
172				.hwnd()
173				.SetWindowPos(
174					HwndPlace::None,
175					POINT::with(rc.left, rc.top),
176					SIZE::with(rc.right - rc.left, rc.bottom - rc.top),
177					co::SWP::NOZORDER | co::SWP::SHOWWINDOW, // show the child of the selected tab
178				)
179				.expect(DONTFAIL);
180		}
181	}
182
183	/// Retrieves one of the associated image lists by sending a
184	/// [`tcm::GetImageList`](crate::msg::tcm::GetImageList) message.
185	///
186	/// Image lists are lazy-initialized: the first time you call this method
187	/// for a given image list, it will be created and assigned with
188	/// [`tcm::SetImageList`](crate::msg::tcm::SetImageList).
189	///
190	/// The image list is owned by the control.
191	#[must_use]
192	pub fn image_list(&self) -> HrResult<HIMAGELIST> {
193		match unsafe { self.hwnd().SendMessage(tcm::GetImageList {}) } {
194			Some(h) => Ok(h), // already created
195			None => {
196				// Not created yet. Create a new image list and assign it to the list view.
197				let h = HIMAGELIST::Create(SIZE::with(16, 16), co::ILC::COLOR32, 1, 1)?.leak();
198				unsafe {
199					self.hwnd()
200						.SendMessage(tcm::SetImageList { himagelist: Some(h.raw_copy()) });
201				}
202				Ok(h)
203			},
204		}
205	}
206
207	/// Item methods.
208	#[must_use]
209	pub const fn items(&self) -> TabItems {
210		TabItems::new(self)
211	}
212
213	/// Sets or unsets the given extended list view styles by sending a
214	/// [`tcm::SetExtendedStyle`](crate::msg::tcm::SetExtendedStyle) message.
215	pub fn set_extended_style(&self, set: bool, ex_style: co::TCS_EX) {
216		unsafe {
217			self.hwnd().SendMessage(tcm::SetExtendedStyle {
218				mask: ex_style,
219				style: if set { ex_style } else { co::TCS_EX::NoValue },
220			});
221		}
222	}
223}
224
225/// Options to create a [`Tab`](crate::gui::Tab) programmatically with
226/// [`Tab::new`](crate::gui::Tab::new).
227pub struct TabOpts {
228	/// Left and top position coordinates of control within parent's client
229	/// area, to be
230	/// [created](https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-createwindowexw).
231	///
232	/// Defaults to `gui::dpi(0, 0)`.
233	pub position: (i32, i32),
234	/// Width and height of control to be
235	/// [created](https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-createwindowexw).
236	///
237	/// Defaults to `gui::dpi(80, 50)`.
238	pub size: (i32, i32),
239	/// Tab styles to be
240	/// [created](https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-createwindowexw).
241	///
242	/// Defaults to `TCS::NoValue`.
243	pub control_style: co::TCS,
244	/// Extended tab styles to be
245	/// [created](https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-createwindowexw).
246	///
247	/// Defaults to `TCS_EX::NoValue`.
248	pub control_ex_style: co::TCS_EX,
249	/// Window styles to be
250	/// [created](https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-createwindowexw).
251	///
252	/// Defaults to `WS::CHILD | WS::GROUP | WS::TABSTOP | WS::VISIBLE`.
253	pub window_style: co::WS,
254	/// Extended window styles to be
255	/// [created](https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-createwindowexw).
256	///
257	/// Defaults to `WS_EX::LEFT`.
258	pub window_ex_style: co::WS_EX,
259
260	/// The control ID.
261	///
262	/// Defaults to an auto-generated ID.
263	pub ctrl_id: u16,
264	/// Horizontal and vertical behavior of the control when the parent window
265	/// is resized.
266	///
267	/// Defaults to `(gui::Horz::None, gui::Vert::None)`.
268	pub resize_behavior: (Horz, Vert),
269
270	/// Items to be added as soon as the control is created. The tuple contains
271	/// the title of the tab and the window to be rendered inside of it.
272	///
273	/// Note that, in o order to make the focus rotation work properly, the
274	/// child windows must be created with the
275	/// [`co::WS_EX::CONTROLPARENT`](crate::co::WS_EX::CONTROLPARENT) extended
276	/// style.
277	///
278	/// Defaults to none.
279	pub items: Vec<(String, Box<dyn AsRef<WindowControl>>)>,
280}
281
282impl Default for TabOpts {
283	fn default() -> Self {
284		Self {
285			position: dpi(0, 0),
286			size: dpi(80, 50),
287			control_style: co::TCS::NoValue,
288			control_ex_style: co::TCS_EX::NoValue,
289			window_style: co::WS::CHILD | co::WS::GROUP | co::WS::TABSTOP | co::WS::VISIBLE,
290			window_ex_style: co::WS_EX::LEFT,
291			ctrl_id: 0,
292			resize_behavior: (Horz::None, Vert::None),
293			items: Vec::new(),
294		}
295	}
296}